package testing;

import api.MarketFactory;
import api.betfairAPI.BetfairExchangeMarket;
import api.betfairAPI.BetfairMarket;
import api.betfairAPI.BetfairParseMarket;
import api.MarketType;
import com.betfair.www.publicapi.types.exchange.v5.ArrayOfRunner;
import com.betfair.www.publicapi.types.exchange.v5.MarketStatusEnum;
import com.betfair.www.publicapi.types.exchange.v5.Runner;
import com.google.common.base.Optional;
import org.apache.log4j.PropertyConfigurator;
import org.jmock.Expectations;
import org.jmock.auto.Mock;
import org.jmock.integration.junit4.JMock;
import org.jmock.integration.junit4.JUnit4Mockery;
import org.jmock.lib.legacy.ClassImposteriser;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import utils.FileUtilities;

import java.io.IOException;
import java.text.ParseException;
import java.util.Calendar;
import java.util.GregorianCalendar;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;

/**
 * User: Darren
 * Date: 3/02/13
 * Time: 7:57 AM
 */
@RunWith(JMock.class)
public class BetfairParseMarketTest {
    private JUnit4Mockery context = new JUnit4Mockery(){{
        setImposteriser(ClassImposteriser.INSTANCE);
    }};
    @Mock private BetfairExchangeMarket mockBetfairExchangeMarket;
    @Mock private com.betfair.www.publicapi.types.exchange.v5.Market mockMarket;
    @Mock private MarketStatusEnum mockMarketStatusEnum;
    @Mock private ArrayOfRunner mockArrayOfRunners;
    @Mock private Runner mockRunner0;
    @Mock private Runner mockRunner1;
    @Mock private Runner mockRunner2;
    @Mock private Runner mockRunner3;
    @Mock private Runner mockRunner4;
    @Mock private Runner mockRunner5;
    @Mock private Runner mockRunner6;

    private void setMarketDataExpectations() {
        context.checking(new Expectations() {{
            exactly(5).of(mockBetfairExchangeMarket).getMarket(); will(returnValue(mockMarket));
            oneOf(mockMarket).getName(); will(returnValue("mockMarketName"));
            oneOf(mockMarket).getMarketId(); will(returnValue(100626715));
            oneOf(mockBetfairExchangeMarket).getExchangeName(); will(returnValue("mockExchangeName"));
            oneOf(mockBetfairExchangeMarket).getExchangeId(); will(returnValue(1));
            GregorianCalendar cal = new GregorianCalendar();
            cal.set(Calendar.HOUR_OF_DAY, 10); cal.set(Calendar.MINUTE, 35);
            oneOf(mockMarket).getMarketTime(); will(returnValue(cal));
            oneOf(mockMarket).getMarketStatus(); will(returnValue(mockMarketStatusEnum));
            oneOf(mockMarket).getCountryISO3(); will(returnValue("AUS"));
        }});
    }
    private void setRunnerExpectations(final Runner[] runners, final int[] runnerSelectionIds) {
        assertTrue(runners.length == runnerSelectionIds.length);
        context.checking(new Expectations() {{
            oneOf(mockBetfairExchangeMarket).getMarket(); will(returnValue(mockMarket));
            oneOf(mockMarket).getRunners(); will(returnValue(mockArrayOfRunners));
            oneOf(mockArrayOfRunners).getRunner(); will(returnValue(runners));
        }});
        for (int i = 0; i < runners.length; ++i) {
            final int idx = i;
            context.checking(new Expectations() {{
                oneOf(runners[idx]).getSelectionId(); will(returnValue(runnerSelectionIds[idx]));
                oneOf(runners[idx]).getName(); will(returnValue("mockRunner" + idx));
            }});
        }
    }

    @Before
    public void setupTest() {
        PropertyConfigurator.configure("config/test-log4j.properties");
    }

    @Test
    public void parseMarketWithRemovedRunner() throws ParseException {
        System.out.println("Executing: testing.BetfairParseMarketTest.parseMarketWithRemovedRunner");
        BetfairMarket market = (BetfairMarket)new MarketFactory().getMarket(MarketType.BETFAIR_MARKET);
        assertNotNull(market);
        BetfairParseMarket parseMarket = new BetfairParseMarket();
        assertNotNull(parseMarket);

        setMarketDataExpectations();
        setRunnerExpectations(
            new Runner[] {mockRunner0, mockRunner1, mockRunner2, mockRunner3, mockRunner4, mockRunner5, mockRunner6},
            new int[]{7030264, 6368961, 7030265, 5881366, 5037588, 6405247, 5162347});

        parseMarket.parseBetfairExchangeMarket(MockBetfairMarket.getMarketWithRemovedRunner(),
                mockBetfairExchangeMarket, market);
        assertTrue(market.getRemovedRunners().size() == 1);
        Optional<BetfairMarket.RemovedRunner> removedRunnerOptional = market.getExistingRemovedRunner(0);
        assertTrue(removedRunnerOptional.isPresent());
        BetfairMarket.RemovedRunner removedRunner = removedRunnerOptional.get();
        assertTrue(removedRunner.getName().equals("1. Cool Partner"));
        assertTrue(removedRunner.getRemovedDate().getHours() == 20);
        assertTrue(removedRunner.getRemovedDate().getMinutes() == 57);
        assertTrue(removedRunner.getReductionFactor().equals("11.2"));
        Optional<BetfairMarket.RemovedRunner> removedRunnerOptional1 = market.getExistingRemovedRunner(1);
        assertFalse(removedRunnerOptional1.isPresent());
    }

    @Test
    public void parseMarketWithBackAndLayPrices() throws IOException, ParseException {
        System.out.println("Executing: testing.BetfairParseMarketTest.parseMarketWithBackAndLayPrices");
        BetfairMarket market = (BetfairMarket)new MarketFactory().getMarket(MarketType.BETFAIR_MARKET);
        assertNotNull(market);
        BetfairParseMarket parseMarket = new BetfairParseMarket();
        assertNotNull(parseMarket);
        String marketWithBackAndLayPrices = FileUtilities.readFileToString(this, "resources/marketWithBackAndLayPrices.txt");
        assertNotNull(marketWithBackAndLayPrices);

        setMarketDataExpectations();
        setRunnerExpectations(new Runner[] {mockRunner0, mockRunner1, mockRunner2}, new int[] {75104, 49844, 58805});

        parseMarket.parseBetfairExchangeMarket(marketWithBackAndLayPrices,
                mockBetfairExchangeMarket, market);
        checkMarketAttributes(market);
        assertTrue(market.getRemovedRunners().size() == 0);
        // Check runner1's attributes
        Optional<BetfairMarket.Runner> runner1Optional = market.getExistingRunner(75104);
        assertTrue(runner1Optional.isPresent());
        BetfairMarket.Runner runner1 = runner1Optional.get();
        checkRunner1Attributes(runner1);
        // Check runner1's back prices
        assertTrue(runner1.getBackPricesSize() == 3);
        checkPrice(runner1.getBackPrice(0), 1.88, 5.88, "L", 1);
        checkPrice(runner1.getBackPrice(1), 1.87, 221.43, "L", 2);
        checkPrice(runner1.getBackPrice(2), 1.86, 12.78, "L", 3);
        // Check runner1's lay prices
        assertTrue(runner1.getLayPricesSize() == 3);
        checkPrice(runner1.getLayPrice(0), 1.89, 149.67, "B", 1);
        checkPrice(runner1.getLayPrice(1), 1.9, 1594.15, "B", 2);
        checkPrice(runner1.getLayPrice(2), 1.93, 22.56, "B", 3);
    }

    @Test
    public void parseMarketWithBackPrices() throws ParseException {
        System.out.println("Executing: testing.BetfairParseMarketTest.parseMarketWithBackPrices");
        BetfairMarket market = (BetfairMarket)new MarketFactory().getMarket(MarketType.BETFAIR_MARKET);
        assertNotNull(market);
        BetfairParseMarket parseMarket = new BetfairParseMarket();
        assertNotNull(parseMarket);

        setMarketDataExpectations();
        setRunnerExpectations(new Runner[] {mockRunner0, mockRunner1, mockRunner2}, new int[]{75104, 49844, 58805});

        parseMarket.parseBetfairExchangeMarket(MockBetfairMarket.getMarketWithBackPrices(),
                mockBetfairExchangeMarket, market);
        checkMarketAttributes(market);
        assertTrue(market.getRemovedRunners().size() == 0);
        // Check runner1's attributes
        Optional<BetfairMarket.Runner> runner1Optional = market.getExistingRunner(75104);
        assertTrue(runner1Optional.isPresent());
        BetfairMarket.Runner runner1 = runner1Optional.get();
        checkRunner1Attributes(runner1);
        // Check runner1's back prices
        assertTrue(runner1.getBackPricesSize() == 3);
        checkPrice(runner1.getBackPrice(0), 1.88, 5.88, "L", 1);
        checkPrice(runner1.getBackPrice(1), 1.87, 221.43, "L", 2);
        checkPrice(runner1.getBackPrice(2), 1.86, 12.78, "L", 3);
        assertTrue(runner1.getLayPricesSize() == 0);
    }

    @Test
    public void parseMarketWithLayPrices() throws ParseException {
        System.out.println("Executing: testing.BetfairParseMarketTest.parseMarketWithLayPrices");
        BetfairMarket market = (BetfairMarket)new MarketFactory().getMarket(MarketType.BETFAIR_MARKET);
        assertNotNull(market);
        BetfairParseMarket parseMarket = new BetfairParseMarket();
        assertNotNull(parseMarket);

        setMarketDataExpectations();
        setRunnerExpectations(new Runner[] {mockRunner0, mockRunner1, mockRunner2}, new int[] {75104, 49844, 58805});

        parseMarket.parseBetfairExchangeMarket(MockBetfairMarket.getMarketWithLayPrices(),
                mockBetfairExchangeMarket, market);
        checkMarketAttributes(market);
        assertTrue(market.getRemovedRunners().size() == 0);
        // Check runner1's attributes
        Optional<BetfairMarket.Runner> runner1Optional = market.getExistingRunner(75104);
        assertTrue(runner1Optional.isPresent());
        BetfairMarket.Runner runner1 = runner1Optional.get();
        checkRunner1Attributes(runner1);
        // Check runner1's back prices
        assertTrue(runner1.getBackPricesSize() == 0);
        assertTrue(runner1.getLayPricesSize() == 3);
        checkPrice(runner1.getLayPrice(0), 1.89, 149.67, "B", 1);
        checkPrice(runner1.getLayPrice(1), 1.9, 1594.15, "B", 2);
        checkPrice(runner1.getLayPrice(2), 1.93, 22.56, "B", 3);
    }

    private void checkMarketAttributes(BetfairMarket market) {
        assertTrue(market.getMarketId() == 108092990);
        assertTrue(market.getCurrency().equals("AUD"));
        assertTrue(market.getMarketStatus().equals("ACTIVE"));
        assertTrue(market.getInPlayDelay() == 0);
        assertTrue(market.getNumberOrWinners() == 1);
        assertTrue(market.getMarketInformation().equals(""));
        assertTrue(market.getDiscountAllowed());
        assertTrue(market.getMarketBaseRate().equals("5.0"));
        assertTrue(market.getRefreshTime() == 1359838295547L);
        assertFalse(market.getbSPMarket());
    }

    private void checkRunner1Attributes(BetfairMarket.Runner runner) {
        assertTrue(runner.getSelectionId() == 75104);
        assertTrue(runner.getOrderIndex() == 1);
        assertTrue(runner.getTotalAmountMatched() == 12000.23);
        assertTrue(runner.getLastPriceMatched() == 1.88);
        assertTrue(runner.getHandicap() == 0.0d);
        assertTrue(runner.getReductionFactor() == 0.0d);
        assertFalse(runner.getVacant());
        assertTrue(runner.getFarSPPrice() == 0.0d);
        assertTrue(runner.getNearSPPrice() == 0.0d);
        assertTrue(runner.getActualSPPrice() == 0.0d);
    }

    private void checkPrice(BetfairMarket.Price price, double p, double amountAvailable, String type, int depth) {
        assertTrue(price.getPrice() == p);
        assertTrue(price.getAmountAvailable() == amountAvailable);
        assertTrue(price.getType().equals(type));
        assertTrue(price.getDepth() == depth);
    }
}
